Categories
React

React Testing — Timers, and Snapshots

Spread the love

Automated tests are important for most apps.

In this article, we’ll take a look at how to write tests for React components.

Timers

We can mock timers in our React component tests.

For example, if we have:

import React, { useEffect } from "react";

export default function Card({ onSelect }) {
  useEffect(() => {
    const timeoutID = setTimeout(() => {
      onSelect(null);
    }, 5000);
    return () => {
      clearTimeout(timeoutID);
    };
  }, [onSelect]);

  return (
    <>
      <button
        data-testid='button'
        onClick={() => onSelect(1)}
      >
        button
    </button>
    </>
  );
}

Then we can test it by writing the following:

Card.test.js

import React from "react";
import { render, unmountComponentAtNode } from "react-dom";
import { act } from "react-dom/test-utils";

import Card from "./card";

jest.useFakeTimers();

let container = null;
beforeEach(() => {
  container = document.createElement("div");
  document.body.appendChild(container);
});

afterEach(() => {
  unmountComponentAtNode(container);
  container.remove();
  container = null;
});

it("should select null after timing out", () => {
  const onSelect = jest.fn();
  act(() => {
    render(<Card onSelect={onSelect} />, container);
  });

  act(() => {
    jest.advanceTimersByTime(100);
  });
  expect(onSelect).not.toHaveBeenCalled();

  act(() => {
    jest.advanceTimersByTime(5000);
  });
  expect(onSelect).toHaveBeenCalledWith(null);
});

it("should clean up on being removed", () => {
  const onSelect = jest.fn();
  act(() => {
    render(<Card onSelect={onSelect} />, container);
  });
  act(() => {
    render(null, container);
  });
  act(() => {
    jest.advanceTimersByTime(5000);
  });
  expect(onSelect).not.toHaveBeenCalled();
});

it("should accept selections", () => {
  const onSelect = jest.fn();
  act(() => {
    render(<Card onSelect={onSelect} />, container);
  });
  act(() => {
    container
      .querySelector("[data-testid='button']")
      .dispatchEvent(new MouseEvent("click", { bubbles: true }));
  });
  expect(onSelect).toHaveBeenCalledWith(1);
});

We have the usual beforeEach and afterEach hooks to create and remove the container for mounting our component for testing.

In the ‘should select null after timing out’, we set the timer to the time we want by calling the jest.advanceTimerByTime method.

Then we check what the mockedonSelect function is called with toHaveBeenCalledWith .

And we check if onSelect is called with toHaveBeenCalled .

In the “should clean up on being removed” test, we make sure that onSelect isn’t called after it’s unmounted.

In the “should accept selections” test, we triggered the click event on the button.

Then we check is onSelect is called with the argument we expect.

Snapshot Testing

We can test snapshots, which is the rendered component at a given time.

For example, if we have:

import React from "react";

export default function Hello({ name }) {
  return (
    <>
      <p>hello {name}</p>
    </>
  );
}

Then we can add the following test to test the component:

Hello.test.js

import React from "react";
import { render, unmountComponentAtNode } from "react-dom";
import { act } from "react-dom/test-utils";
import pretty from "pretty";
import Hello from "./hello";

let container = null;
beforeEach(() => {
  container = document.createElement("div");
  document.body.appendChild(container);
});

afterEach(() => {
  unmountComponentAtNode(container);
  container.remove();
  container = null;
});

it("should render a greeting", () => {
  act(() => {
    render(<Hello />, container);
  });
  expect(pretty(container.innerHTML)).toMatchInlineSnapshot(`"<p>hello </p>"`);

  act(() => {
    render(<Hello name="james" />, container);
  });
  expect(pretty(container.innerHTML)).toMatchInlineSnapshot(
    `"<p>hello james</p>"`
  );
});

We have the same beforeEach and afterEach hooks as before.

To test the component, we get the rendered HTML with container.innerHTML .

Then we check it against the HTML code with toMatchInlineSnapshot .

We have to install pretty and prettier by running:

npm i pretty prettier --save-dev

to get the pretty function to render the HTML.

Conclusion

We can mock timers and test React components with rendered HTML.

By John Au-Yeung

Web developer specializing in React, Vue, and front end development.

Leave a Reply

Your email address will not be published. Required fields are marked *